001 /*
002 * Copyright 2006 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.lang;
020
021 import java.io.File;
022 import java.io.IOException;
023 import java.net.URI;
024 import java.net.URL;
025 import java.util.Map;
026 import java.util.Hashtable;
027 import java.lang.ref.WeakReference;
028
029 import net.dpml.transit.link.ArtifactLinkManager;
030
031 import net.dpml.util.Logger;
032 import net.dpml.util.DefaultLogger;
033 import net.dpml.util.ElementHelper;
034 import net.dpml.util.DOM3DocumentBuilder;
035 import net.dpml.util.Decoder;
036 import net.dpml.util.DecoderFactory;
037 import net.dpml.util.DecodingException;
038 import net.dpml.util.Resolver;
039 import net.dpml.util.SimpleResolver;
040
041 import org.w3c.dom.Document;
042 import org.w3c.dom.Element;
043 import org.w3c.dom.TypeInfo;
044
045 /**
046 * Construct a part.
047 */
048 public final class PartDecoder implements Decoder
049 {
050 /**
051 * Part XSD uri.
052 */
053 public static final String PART_XSD_URI = "link:xsd:dpml/lang/dpml-part#1.0";
054
055 private static final DOM3DocumentBuilder DOCUMENT_BUILDER =
056 new DOM3DocumentBuilder();
057
058 private static final ValueDecoder VALUE_DECODER = new ValueDecoder();
059
060 private static final PartDecoder DECODER = new PartDecoder();
061
062 private static final String BASEPATH = setupBasePathSpec();
063
064 /**
065 * Get the singleton instance of the part decoder.
066 * @return the decoder instance.
067 */
068 public static PartDecoder getInstance()
069 {
070 return DECODER;
071 }
072
073 private Map m_map = new Hashtable();
074 private Map m_builders = new Hashtable();
075
076 private Logger m_logger;
077
078 private PartDecoder()
079 {
080 m_logger = new DefaultLogger( "dpml.lang" );
081 }
082
083 /**
084 * Load a part from a uri.
085 * @param uri the part uri
086 * @param cache if true parts are cached relative to the requested uri
087 * @return the part definition
088 * @exception IOException if an IO error occurs
089 */
090 public Part loadPart( URI uri, boolean cache ) throws IOException
091 {
092 if( null == uri )
093 {
094 throw new NullPointerException( "uri" );
095 }
096 if( getLogger().isDebugEnabled() )
097 {
098 String path = getPartSpec( uri );
099 if( getLogger().isTraceEnabled() )
100 {
101 if( cache )
102 {
103 getLogger().trace( "loading part (cache enabled): " + path );
104 }
105 else
106 {
107 getLogger().trace( "loading part (cache disabled): " + path );
108 }
109 }
110 else
111 {
112 getLogger().debug( "loading part: " + path );
113 }
114 }
115 String key = buildKey( uri );
116 if( cache )
117 {
118 WeakReference ref = (WeakReference) m_map.get( key );
119 if( null != ref )
120 {
121 Part part = (Part) ref.get();
122 if( null != part )
123 {
124 if( getLogger().isDebugEnabled() )
125 {
126 getLogger().debug( "located part in cache" );
127 }
128 return part;
129 }
130 }
131 }
132
133 // cache based retrieval was disabled or no cache value present
134
135 try
136 {
137 final Document document = DOCUMENT_BUILDER.parse( uri );
138 final Element root = document.getDocumentElement();
139 Resolver resolver = new SimpleResolver();
140 Part value = decodePart( uri, root, resolver );
141 if( cache )
142 {
143 WeakReference reference = new WeakReference( value );
144 m_map.put( key, reference );
145 if( getLogger().isTraceEnabled() )
146 {
147 getLogger().trace( "caching part"
148 + "\n uri: " + uri
149 + "\n key: " + key );
150 }
151 }
152 return value;
153 }
154 catch( Throwable e )
155 {
156 final String error =
157 "An error while attempting to load a part."
158 + "\n uri: " + uri;
159 IOException exception = new IOException( error );
160 exception.initCause( e );
161 throw exception;
162 }
163 }
164
165 private String buildKey( URI uri ) throws IOException
166 {
167 ClassLoader classloader = getAnchorClassLoader();
168 int n = System.identityHashCode( classloader );
169 return "" + n + "#" + getRealURI( uri ).toASCIIString();
170 }
171
172 private String getID()
173 {
174 ClassLoader classloader = getAnchorClassLoader();
175 int n = System.identityHashCode( classloader );
176 return "" + n;
177 }
178
179 private URI getRealURI( URI uri ) throws IOException
180 {
181 if( "link".equals( uri.getScheme() ) )
182 {
183 ArtifactLinkManager manager = new ArtifactLinkManager();
184 return manager.getTargetURI( uri );
185 }
186 else
187 {
188 return uri;
189 }
190 }
191
192 /**
193 * Resolve a object from a DOM element.
194 * @param element the dom element
195 * @param resolver build-time value resolver
196 * @return the resolved object
197 * @exception IOException if an error occurs during element evaluation
198 */
199 public Object decode( Element element, Resolver resolver ) throws IOException
200 {
201 return decodePart( null, element, resolver );
202 }
203
204 /**
205 * Resolve a part from a DOM element.
206 * @param uri the part uri
207 * @param element element part definition
208 * @param resolver build-time value resolver
209 * @return the resolved part datastructure
210 * @exception IOException if an error occurs during element evaluation
211 */
212 public Part decodePart( URI uri, Element element, Resolver resolver ) throws IOException
213 {
214 TypeInfo info = element.getSchemaTypeInfo();
215 String namespace = info.getTypeNamespace();
216 if( PART_XSD_URI.equals( namespace ) )
217 {
218 boolean alias = ElementHelper.getBooleanAttribute( element, "alias", false );
219 Info information = getInfo( uri, element );
220 Classpath classpath = getClasspath( element );
221 Element strategy = getStrategyElement( element );
222 return build( m_logger, information, classpath, strategy, resolver );
223 }
224 else
225 {
226 final String error =
227 "Part namespace not recognized."
228 + "\nExpecting: " + PART_XSD_URI
229 + "\nFound: " + namespace;
230 throw new DecodingException( element, error );
231 }
232 }
233
234 /**
235 * Resolve a part plugin or resource strategy.
236 * @param logger the logging channel
237 * @param information the part info definition
238 * @param classpath the part classpath definition
239 * @param strategy part deployment strategy definition
240 * @param resolver build-time value resolver
241 * @return the resolved part
242 * @exception IOException if an error occurs during element evaluation
243 */
244 public Part build(
245 Logger logger, Info information, Classpath classpath, Element strategy, Resolver resolver )
246 throws IOException
247 {
248 ClassLoader anchor = getAnchorClassLoader();
249 TypeInfo info = strategy.getSchemaTypeInfo();
250 String namespace = info.getTypeNamespace();
251 if( PART_XSD_URI.equals( namespace ) )
252 {
253 // this is either a plugin or a resource
254
255 String name = info.getTypeName();
256 if( "plugin".equals( name ) )
257 {
258 if( logger.isTraceEnabled() )
259 {
260 logger.trace( "reading plugin definition" );
261 }
262 String classname = ElementHelper.getAttribute( strategy, "class" );
263 Element[] elements = ElementHelper.getChildren( strategy );
264 Value[] values = VALUE_DECODER.decodeValues( elements );
265 Part part = new Plugin( logger, information, classpath, classname, values );
266 if( logger.isTraceEnabled() )
267 {
268 logger.trace( "loaded plugin definition" );
269 }
270 return part;
271 }
272 else if( "resource".equals( name ) )
273 {
274 if( logger.isTraceEnabled() )
275 {
276 logger.trace( "reading resource definition" );
277 }
278 String urn = ElementHelper.getAttribute( strategy, "urn" );
279 String path = ElementHelper.getAttribute( strategy, "path" );
280 Part part = new Resource( logger, information, classpath, urn, path );
281 if( logger.isTraceEnabled() )
282 {
283 logger.trace( "loaded resource definition" );
284 }
285 return part;
286 }
287 else
288 {
289 final String error =
290 "Element type name ["
291 + name
292 + "] is not a recognized element type within the "
293 + PART_XSD_URI
294 + " namespace.";
295 throw new DecodingException( strategy, error );
296 }
297 }
298 else
299 {
300 // this is a foreign part
301
302 try
303 {
304 URI uri = getDecoderURI( strategy );
305 Builder builder = loadForeignBuilder( uri );
306 if( logger.isTraceEnabled() )
307 {
308 logger.trace(
309 "using builder ["
310 + builder.getClass().getName()
311 + "]" );
312 }
313 Part part = builder.build( logger, information, classpath, strategy, resolver );
314 if( logger.isTraceEnabled() )
315 {
316 logger.trace(
317 "loaded part ["
318 + part.getClass().getName()
319 + "]" );
320 }
321 return part;
322 }
323 catch( Exception ioe )
324 {
325 final String error =
326 "Internal error while attempting to load foreign part.";
327 throw new DecodingException( strategy, error, ioe );
328 }
329 finally
330 {
331 Thread.currentThread().setContextClassLoader( anchor );
332 }
333 }
334 }
335
336 /**
337 * Resolve the element decoder uri.
338 *
339 * @param element the DOM element
340 * @return the decoder uri
341 * @exception DecodingException if an error occurs
342 */
343 public URI getDecoderURI( Element element ) throws DecodingException
344 {
345 String uri = ElementHelper.getAttribute( element, "handler" );
346 if( null != uri )
347 {
348 try
349 {
350 return new URI( uri );
351 }
352 catch( Exception e )
353 {
354 final String error =
355 "Internal error while resolving handler attribute (expecting uri value)";
356 throw new DecodingException( element, error, e );
357 }
358 }
359 TypeInfo info = element.getSchemaTypeInfo();
360 String namespace = info.getTypeNamespace();
361 try
362 {
363 return DecoderFactory.getDecoderURIFromNamespaceURI( namespace );
364 }
365 catch( Exception e )
366 {
367 final String error =
368 "Internal error while attempting to resolve default decoder uri.";
369 throw new DecodingException( element, error, e );
370 }
371 }
372
373 /**
374 * Get the assigned logging channel.
375 * @return the logging channel
376 */
377 protected Logger getLogger()
378 {
379 return m_logger;
380 }
381
382 /**
383 * Load a forign part builder. The implementation will attempt to resolve a
384 * plugin defintion from the supplied uri, caching a reference to
385 * the builder, and returning the plugin instance as a builder instance.
386 *
387 * @param uri the part builder uri
388 * @see Builder
389 * @exception DecodingException if a part decoding error occurs
390 * @exception Exception if part loading error occurs
391 */
392 private Builder loadForeignBuilder( URI uri ) throws DecodingException, Exception
393 {
394 WeakReference ref = (WeakReference) m_builders.get( uri );
395 if( null != ref )
396 {
397 Builder builder = (Builder) ref.get();
398 if( null != builder )
399 {
400 if( getLogger().isTraceEnabled() )
401 {
402 getLogger().trace( "located builder [" + uri + "]" );
403 }
404 return builder;
405 }
406 else
407 {
408 if( getLogger().isTraceEnabled() )
409 {
410 getLogger().trace( "reloading builder [" + uri + "]" );
411 }
412 }
413 }
414 else
415 {
416 if( getLogger().isTraceEnabled() )
417 {
418 getLogger().trace( "loading builder [" + uri + "]" );
419 }
420 }
421
422 Part part = loadPart( uri, true );
423 Logger logger = getLogger();
424 Object[] args = new Object[]{logger};
425 Object object = part.instantiate( args );
426 if( object instanceof Builder )
427 {
428 Builder builder = (Builder) object;
429 WeakReference reference = new WeakReference( builder );
430 m_builders.put( uri, reference );
431 return builder;
432 }
433 else
434 {
435 final String error =
436 "Plugin does not implement the "
437 + Builder.class.getName()
438 + " interface."
439 + "\nURI: " + uri
440 + "\nClass: " + object.getClass().getName();
441 throw new PartException( error );
442 }
443 }
444
445 private Element getStrategyElement( Element root ) throws DecodingException
446 {
447 Element[] children = ElementHelper.getChildren( root );
448 if( children.length != 3 )
449 {
450 final String error =
451 "Illegal number of child elements in <part>. Expecting 3, found "
452 + children.length
453 + ".";
454 throw new DecodingException( root, error );
455 }
456 return children[1];
457 }
458
459
460 private Info getInfo( URI uri, Element root )
461 {
462 Element element = ElementHelper.getChild( root, "info" );
463 String title = ElementHelper.getAttribute( element, "title" );
464 Element descriptionElement = ElementHelper.getChild( element, "description" );
465 String description = ElementHelper.getValue( descriptionElement );
466 return new Info( uri, title, description );
467 }
468
469 /**
470 * Construct the classpath defintion.
471 * @param root the element containing a 'classpath' element.
472 * @return the classpath definition
473 * @exception DecodingException if an error occurs during element evaluation
474 */
475 protected Classpath getClasspath( Element root ) throws DecodingException
476 {
477 Element classpath = ElementHelper.getChild( root, "classpath" );
478 if( null == classpath )
479 {
480 final String error =
481 "Required classpath element is not present in plugin descriptor.";
482 throw new DecodingException( root, error );
483 }
484
485 try
486 {
487 Element[] children = ElementHelper.getChildren( classpath );
488 URI[] sys = buildURIs( classpath, "system" );
489 URI[] pub = buildURIs( classpath, "public" );
490 URI[] prot = buildURIs( classpath, "protected" );
491 URI[] priv = buildURIs( classpath, "private" );
492 Classpath cp = new Classpath( sys, pub, prot, priv );
493 return cp;
494 }
495 catch( Throwable e )
496 {
497 final String error =
498 "Unable to decode classpath due to an unexpected error.";
499 throw new DecodingException( classpath, error, e );
500 }
501 }
502
503 private URI[] buildURIs( Element classpath, String key ) throws Exception
504 {
505 Element category = ElementHelper.getChild( classpath, key );
506 if( null == category )
507 {
508 return new URI[0];
509 }
510 else
511 {
512 Element[] children = ElementHelper.getChildren( category, "uri" );
513 URI[] uris = new URI[ children.length ];
514 for( int i=0; i<children.length; i++ )
515 {
516 Element child = children[i];
517 String value = ElementHelper.getValue( child );
518 uris[i] = new URI( value );
519 }
520 return uris;
521 }
522 }
523
524 private ClassLoader getAnchorClassLoader()
525 {
526 ClassLoader classloader = Thread.currentThread().getContextClassLoader();
527 if( null == classloader )
528 {
529 return Part.class.getClassLoader();
530 }
531 else
532 {
533 return classloader;
534 }
535 }
536
537 private static String setupBasePathSpec()
538 {
539 try
540 {
541 String path = System.getProperty( "user.dir" );
542 File file = new File( path );
543 URI uri = file.toURI();
544 URL url = file.toURL();
545 return url.toString();
546 }
547 catch( Exception e )
548 {
549 return e.toString();
550 }
551 }
552
553 static String getPartSpec( URI uri )
554 {
555 String path = uri.toASCIIString();
556 if( path.startsWith( BASEPATH ) )
557 {
558 return "./" + path.substring( BASEPATH.length() );
559 }
560 else
561 {
562 return path;
563 }
564 }
565 }